define(function (require) {

    'use strict';

    var zrUtil = require('zrender/core/util');
    var graphic = require('../../util/graphic');
    var formatUtil = require('../../util/format');
    var numberUtil = require('../../util/number');

    var MONTH_TEXT = {
        EN: [
                'Jan', 'Feb', 'Mar',
                'Apr', 'May', 'Jun',
                'Jul', 'Aug', 'Sep',
                'Oct', 'Nov', 'Dec'
            ],
        CN: [
                '一月', '二月', '三月',
                '四月', '五月', '六月',
                '七月', '八月', '九月',
                '十月', '十一月', '十二月'
            ]
    };

    var WEEK_TEXT = {
        EN: ['S', 'M', 'T', 'W', 'T', 'F', 'S'],
        CN: ['日', '一', '二', '三', '四', '五', '六']
    };

    return require('../../echarts').extendComponentView({

        type: 'calendar',

        /**
         * top/left line points
         *  @private
         */
        _tlpoints: null,

        /**
         * bottom/right line points
         *  @private
         */
        _blpoints: null,

        /**
         * first day of month
         *  @private
         */
        _firstDayOfMonth: null,

        /**
         * first day point of month
         *  @private
         */
        _firstDayPoints: null,

        render: function (calendarModel, ecModel, api) {

            var group = this.group;

            group.removeAll();

            var coordSys = calendarModel.coordinateSystem;

            // range info
            var rangeData = coordSys.getRangeInfo();
            var orient = coordSys.getOrient();

            this._renderDayRect(calendarModel, rangeData, group);

            // _renderLines must be called prior to following function
            this._renderLines(calendarModel, rangeData, orient, group);

            this._renderYearText(calendarModel, rangeData, orient, group);

            this._renderMonthText(calendarModel, orient, group);

            this._renderWeekText(calendarModel, rangeData, orient, group);
        },

        // render day rect
        _renderDayRect: function (calendarModel, rangeData, group) {
            var coordSys = calendarModel.coordinateSystem;
            var itemRectStyleModel = calendarModel.getModel('itemStyle.normal').getItemStyle();
            var sw = coordSys.getCellWidth();
            var sh = coordSys.getCellHeight();

            for (var i = rangeData.start.time;
                i <= rangeData.end.time;
                i = coordSys.getNextNDay(i, 1).time
            ) {

                var point = coordSys.dataToRect([i], false).tl;

                // every rect
                var rect = new graphic.Rect({
                    shape: {
                        x: point[0],
                        y: point[1],
                        width: sw,
                        height: sh
                    },
                    cursor: 'default',
                    style: itemRectStyleModel
                });

                group.add(rect);
            }

        },

        // render separate line
        _renderLines: function (calendarModel, rangeData, orient, group) {

            var self = this;

            var coordSys = calendarModel.coordinateSystem;

            var lineStyleModel = calendarModel.getModel('splitLine.lineStyle').getLineStyle();
            var show = calendarModel.get('splitLine.show');

            var lineWidth = lineStyleModel.lineWidth;

            this._tlpoints = [];
            this._blpoints = [];
            this._firstDayOfMonth = [];
            this._firstDayPoints = [];


            var firstDay = rangeData.start;

            for (var i = 0; firstDay.time <= rangeData.end.time; i++) {
                addPoints(firstDay.formatedDate);

                if (i === 0) {
                    firstDay = coordSys.getDateInfo(rangeData.start.y + '-' + rangeData.start.m);
                }

                var date = firstDay.date;
                date.setMonth(date.getMonth() + 1);
                firstDay = coordSys.getDateInfo(date);
            }

            addPoints(coordSys.getNextNDay(rangeData.end.time, 1).formatedDate);

            function addPoints(date) {

                self._firstDayOfMonth.push(coordSys.getDateInfo(date));
                self._firstDayPoints.push(coordSys.dataToRect([date], false).tl);

                var points = self._getLinePointsOfOneWeek(calendarModel, date, orient);

                self._tlpoints.push(points[0]);
                self._blpoints.push(points[points.length - 1]);

                show && self._drawSplitline(points, lineStyleModel, group);
            }


            // render top/left line
            show && this._drawSplitline(self._getEdgesPoints(self._tlpoints, lineWidth, orient), lineStyleModel, group);

            // render bottom/right line
            show && this._drawSplitline(self._getEdgesPoints(self._blpoints, lineWidth, orient), lineStyleModel, group);

        },

        // get points at both ends
        _getEdgesPoints: function (points, lineWidth, orient) {
            var rs = [points[0].slice(), points[points.length - 1].slice()];
            var idx = orient === 'horizontal' ? 0 : 1;

            // both ends of the line are extend half lineWidth
            rs[0][idx] = rs[0][idx] - lineWidth / 2;
            rs[1][idx] = rs[1][idx] + lineWidth / 2;

            return rs;
        },

        // render split line
        _drawSplitline: function (points, lineStyleModel, group) {

            var poyline = new graphic.Polyline({
                z2: 20,
                shape: {
                    points: points
                },
                style: lineStyleModel
            });

            group.add(poyline);
        },

        // render month line of one week points
        _getLinePointsOfOneWeek: function (calendarModel, date, orient) {

            var coordSys = calendarModel.coordinateSystem;
            date = coordSys.getDateInfo(date);

            var points = [];

            for (var i = 0; i < 7; i++) {

                var tmpD = coordSys.getNextNDay(date.time, i);
                var point = coordSys.dataToRect([tmpD.time], false);

                points[2 * tmpD.day] = point.tl;
                points[2 * tmpD.day + 1] = point[orient === 'horizontal' ? 'bl' : 'tr'];
            }

            return points;

        },

        _formatterLabel: function (formatter, params) {

            if (typeof formatter === 'string' && formatter) {
                return formatUtil.formatTplSimple(formatter, params);
            }

            if (typeof formatter === 'function') {
                return formatter(params);
            }

            return params.nameMap;

        },

        _yearTextPositionControl: function (textEl, point, orient, position, margin) {

            point = point.slice();
            var aligns = ['center', 'bottom'];

            if (position === 'bottom') {
                point[1] += margin;
                aligns = ['center', 'top'];
            }
            else if (position === 'left') {
                point[0] -= margin;
            }
            else if (position === 'right') {
                point[0] += margin;
                aligns = ['center', 'top'];
            }
            else { // top
                point[1] -= margin;
            }

            var rotate = 0;
            if (position === 'left' || position === 'right') {
                rotate = Math.PI / 2;
            }

            return {
                rotation: rotate,
                position: point,
                style: {
                    textAlign: aligns[0],
                    textVerticalAlign: aligns[1]
                }
            };
        },

        // render year
        _renderYearText: function (calendarModel, rangeData, orient, group) {
            var yearLabel = calendarModel.getModel('yearLabel');

            if (!yearLabel.get('show')) {
                return;
            }

            var margin = yearLabel.get('margin');
            var pos = yearLabel.get('position');

            if (!pos) {
                pos = orient !== 'horizontal' ? 'top' : 'left';
            }

            var points = [this._tlpoints[this._tlpoints.length - 1], this._blpoints[0]];
            var xc = (points[0][0] + points[1][0]) / 2;
            var yc = (points[0][1] + points[1][1]) / 2;

            var idx = orient === 'horizontal' ? 0 : 1;

            var posPoints = {
                top: [xc, points[idx][1]],
                bottom: [xc, points[1 - idx][1]],
                left: [points[1 - idx][0], yc],
                right: [points[idx][0], yc]
            };

            var name = rangeData.start.y;

            if (+rangeData.end.y > +rangeData.start.y) {
                name = name + '-' + rangeData.end.y;
            }

            var formatter = yearLabel.get('formatter');

            var params = {
                start: rangeData.start.y,
                end: rangeData.end.y,
                nameMap: name
            };

            var content = this._formatterLabel(formatter, params);

            var yearText = new graphic.Text({z2: 30});
            graphic.setTextStyle(yearText.style, yearLabel, {text: content}),
            yearText.attr(this._yearTextPositionControl(yearText, posPoints[pos], orient, pos, margin));

            group.add(yearText);
        },

        _monthTextPositionControl: function (point, isCenter, orient, position, margin) {
            var align = 'left';
            var vAlign = 'top';
            var x = point[0];
            var y = point[1];

            if (orient === 'horizontal') {
                y = y + margin;

                if (isCenter) {
                    align = 'center';
                }

                if (position === 'start') {
                    vAlign = 'bottom';
                }
            }
            else {
                x = x + margin;

                if (isCenter) {
                    vAlign = 'middle';
                }

                if (position === 'start') {
                    align = 'right';
                }
            }

            return {
                x: x,
                y: y,
                textAlign: align,
                textVerticalAlign: vAlign
            };
        },

        // render month and year text
        _renderMonthText: function (calendarModel, orient, group) {
            var monthLabel = calendarModel.getModel('monthLabel');

            if (!monthLabel.get('show')) {
                return;
            }

            var nameMap = monthLabel.get('nameMap');
            var margin = monthLabel.get('margin');
            var pos = monthLabel.get('position');
            var align = monthLabel.get('align');

            var termPoints = [this._tlpoints, this._blpoints];

            if (zrUtil.isString(nameMap)) {
                nameMap = MONTH_TEXT[nameMap.toUpperCase()] || [];
            }

            var idx = pos === 'start' ? 0 : 1;
            var axis = orient === 'horizontal' ? 0 : 1;
            margin = pos === 'start' ? -margin : margin;
            var isCenter = (align === 'center');

            for (var i = 0; i < termPoints[idx].length - 1; i++) {

                var tmp = termPoints[idx][i].slice();
                var firstDay = this._firstDayOfMonth[i];

                if (isCenter) {
                    var firstDayPoints = this._firstDayPoints[i];
                    tmp[axis] = (firstDayPoints[axis] + termPoints[0][i + 1][axis]) / 2;
                }

                var formatter = monthLabel.get('formatter');
                var name = nameMap[+firstDay.m - 1];
                var params = {
                    yyyy: firstDay.y,
                    yy: (firstDay.y + '').slice(2),
                    MM: firstDay.m,
                    M: +firstDay.m,
                    nameMap: name
                };

                var content = this._formatterLabel(formatter, params);

                var monthText = new graphic.Text({z2: 30});
                zrUtil.extend(
                    graphic.setTextStyle(monthText.style, monthLabel, {text: content}),
                    this._monthTextPositionControl(tmp, isCenter, orient, pos, margin)
                );

                group.add(monthText);
            }
        },

        _weekTextPositionControl: function (point, orient, position, margin, cellSize) {
            var align = 'center';
            var vAlign = 'middle';
            var x = point[0];
            var y = point[1];
            var isStart = position === 'start';

            if (orient === 'horizontal') {
                x = x + margin + (isStart ? 1 : -1) * cellSize[0] / 2;
                align = isStart ? 'right' : 'left';
            }
            else {
                y = y + margin + (isStart ? 1 : -1) * cellSize[1] / 2;
                vAlign = isStart ? 'bottom' : 'top';
            }

            return {
                x: x,
                y: y,
                textAlign: align,
                textVerticalAlign: vAlign
            };
        },

        // render weeks
        _renderWeekText: function (calendarModel, rangeData, orient, group) {
            var dayLabel = calendarModel.getModel('dayLabel');

            if (!dayLabel.get('show')) {
                return;
            }

            var coordSys = calendarModel.coordinateSystem;
            var pos = dayLabel.get('position');
            var nameMap = dayLabel.get('nameMap');
            var margin = dayLabel.get('margin');
            var firstDayOfWeek = coordSys.getFirstDayOfWeek();

            if (zrUtil.isString(nameMap)) {
                nameMap = WEEK_TEXT[nameMap.toUpperCase()] || [];
            }

            var start = coordSys.getNextNDay(
                rangeData.end.time, (7 - rangeData.lweek)
            ).time;

            var cellSize = [coordSys.getCellWidth(), coordSys.getCellHeight()];
            margin = numberUtil.parsePercent(margin, cellSize[orient === 'horizontal' ? 0 : 1]);

            if (pos === 'start') {
                start = coordSys.getNextNDay(
                    rangeData.start.time, -(7 + rangeData.fweek)
                ).time;
                margin = -margin;
            }

            for (var i = 0; i < 7; i++) {

                var tmpD = coordSys.getNextNDay(start, i);
                var point = coordSys.dataToRect([tmpD.time], false).center;
                var day = i;
                day = Math.abs((i + firstDayOfWeek) % 7);
                var weekText = new graphic.Text({z2: 30});

                zrUtil.extend(
                    graphic.setTextStyle(weekText.style, dayLabel, {text: nameMap[day]}),
                    this._weekTextPositionControl(point, orient, pos, margin, cellSize)
                );
                group.add(weekText);
            }
        }
    });
});
